package cn.codeforfun.initialization.utils;

import cn.codeforfun.initialization.server.exception.ServerConnectException;
import cn.codeforfun.initialization.server.model.Server;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

/**
 * @author https://juejin.im/post/5d3e961ee51d45508c2fb941
 */
@Slf4j
@Getter
@Setter
public class JschUtil {
    private static final Integer CONNECT_TIMEOUT = 10000;
    public static final String AUTH_FAIL = "Auth fail";

    private String username;
    private String password;
    private String host;
    private Integer port;

    private Session session;

    private JschUtil() {
    }

    public JschUtil(Server server) throws JSchException {
        this(server.getUsername(), server.getPassword(), server.getHost(), server.getPort());
    }

    private JschUtil(String username, String password, String host, Integer port) throws JSchException {
        this.username = username;
        this.password = password;
        this.host = host;
        this.port = port;
        this.session = getSession();
    }

    public List<String> execute(String command, int timeout) throws JSchException {
        return remoteExecute(session, command, timeout);

    }

    public List<String> execute(String command) throws JSchException {
        return remoteExecute(session, command);
    }

    private Session getSession() throws JSchException {
        JSch jSch = new JSch();
        Session session = jSch.getSession(this.username, this.host, this.port);
        session.setPassword(this.password);
        session.setConfig("StrictHostKeyChecking", "no");
        try {
            session.connect(CONNECT_TIMEOUT);
            return session;
        } catch (JSchException e) {
            if (AUTH_FAIL.equals(e.getMessage())) {
                throw new ServerConnectException();
            }
            e.printStackTrace();
        }
        return null;
    }


    private List<String> remoteExecute(Session session, String command, int timeout) throws JSchException {
        log.debug(">> {}", command);
        List<String> resultLines = new ArrayList<>();
        ChannelExec channel = null;
        try {
            channel = (ChannelExec) session.openChannel("exec");
            channel.setCommand(command);
            InputStream input = channel.getInputStream();
            InputStream error = channel.getErrStream();
            channel.connect(timeout);
            convertToList(resultLines, error);
            convertToList(resultLines, input);
        } catch (IOException e) {
            log.error("IOException:", e);
        } finally {
            if (channel != null) {
                try {
                    channel.disconnect();
                } catch (Exception e) {
                    log.error("JSch channel disconnect error:", e);
                }
            }
        }
        return resultLines;
    }

    private List<String> remoteExecute(Session session, String command) throws JSchException {
        return remoteExecute(session, command, CONNECT_TIMEOUT);
    }

    private void convertToList(List<String> resultLines, InputStream input) throws IOException {
        try {
            BufferedReader inputReader = new BufferedReader(new InputStreamReader(input));
            String inputLine;
            while ((inputLine = inputReader.readLine()) != null) {
                log.debug("   {}", inputLine);
                resultLines.add(inputLine);
            }
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (Exception e) {
                    log.error("JSch inputStream close error:", e);
                }
            }
        }
    }

    public void disconnect() {
        if (session.isConnected()) {
            session.disconnect();
        }
    }

}
